<?php

/*
 * Copyright 2008 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#namespace tehframework\mvc;

class ModelAndView
{
	/**
	 * @var Model
	 */
	private $model;
	
	/**
	 * @var string|View
	 */
	private $view;
	
	/**
	 * Constructs a new ModelAndView instance based on passed arguments.
	 * 
	 * @param  View|string|Model|array<string,mixed?>? $viewNameOrModel
	 *         An instance of View, a view name, an instance of Model
	 *         or an array of model attributes.
	 * @param  string|Model|array<string,mixed?>? $attrNameOrModel
	 *         A name of the model attribute, an instance of Model
	 *         or an array of model attributes.
	 * @param  mixed? $attrValue
	 *         A value of the model attribute.
	 */
	public function __construct(
		$viewNameOrModel = null, $attrNameOrModel = null, $attrValue = null)
	{
		if (is_string($viewNameOrModel) || ($viewNameOrModel instanceof View))
		{
			$this->view = $viewNameOrModel;
		}
		elseif (is_array($viewNameOrModel))
		{
			$this->model = new ModelImpl($viewNameOrModel);
		}
		elseif ($viewNameOrModel instanceof Model)
		{
			$this->model = $viewNameOrModel;
		}
		
		if (empty($this->model))
		{
			$this->model = new ModelImpl();
		}
		
		if (is_string($attrNameOrModel))
		{
			$this->model->addAttribute($attrNameOrModel, $attrValue);
		}
		elseif ($attrNameOrModel !== null)
		{
			$this->model->copyAttributes($attrNameOrModel);
		}
	}
	
	/**
	 * @return string
	 */
	public function __toString()
	{
		return get_class($this);
	}
	
	/**
	 * Sets a view name for this ModelAndView.
	 * 
	 * @param  string $viewName
	 *         A name of the view.
	 * @return ModelAndView
	 */
	public function setViewName($viewName)
	{
		$this->view = $viewName;
		
		return $this;
	}
	
	/**
	 * Returns a view name for this ModelAndView,
	 * or NULL if we are using an object.
	 * 
	 * @return string?
	 *         A view name for this ModelAndView,
	 *         or NULL if we are using an object.
	 */
	public function getViewName()
	{
		return is_string($this->view) ? $this->view : null;
	}
	
	/**
	 * Sets a view object for this ModelAndView.
	 * 
	 * @param  View $view
	 *         An instance of the view object.
	 * @return ModelAndView
	 */
	public function setView(View $view)
	{
		$this->view = $view;
		
		return $this;
	}
	
	/**
	 * Returns a view object for this ModelAndView,
	 * or NULL if we are using a view name.
	 * 
	 * @return View?
	 *         A view object for this ModelAndView,
	 *         or NULL if we are using a view name.
	 */
	public function getView()
	{
		return $this->view instanceof View ? $this->view : null;
	}
	
	/**
	 * Indicates whether or not this ModelAndView has a view, either as a
	 * view name or as a direct {@link View} instance.
	 * 
	 * @return bool
	 *         TRUE if this ModelAndView has a view; otherwise, FALSE.
	 */
	public function hasView()
	{
		return $this->view !== null;
	}
	
	/**
	 * Determines whether we use a view reference (if the view has been specified
	 * via a name).
	 * 
	 * @return boolean
	 *         TRUE if the view has been specified via a name; otherwise, FALSE.
	 */
	public function isReference()
	{
		return is_string($this->view);
	}
	
	/**
	 * Returns the underlying Model instance.
	 * 
	 * @return Model
	 *         The underlying model.
	 */
	public function getModel()
	{
		if ($this->model === null)
		{
			$this->model = new ModelImpl();
		}
		
		return $this->model;
	}
	
	/**
	 * Adds an attribute to the model.
	 * 
	 * @param  string $attributeName
	 *         A name of the attribute to add. Cannot be NULL or empty.
	 * @param  mixed $attributeValue
	 *         A value of the attribute. Can be NULL.
	 * @return ModelAndView
	 * @throws InvalidArgumentException
	 *         If {@link $attributeName} is NULL or empty.
	 */
	public function addObject($attributeName, $attributeValue)
	{
		$this->getModel()->addAttribute($attributeName, $attributeValue);
		
		return $this;
	}
	
	/**
	 * Adds all {@link $attributes} to the model. 
	 * 
	 * @param  Traversable $attributes
	 *         Attributes to add to the model.
	 * @return ModelAndView
	 * @throws InvalidArgumentException
	 *         If name of any of the specified attributes is NULL or empty.
	 */
	public function addAllObjects($attributes)
	{
		$model = $this->getModel();
		
		foreach ($attributes as $name => $value)
		{
			$this->model->addAttribute($name, $value);
		}
		
		return $this;
	}
	
	/**
	 * Clears the state of this ModelAndView object.
	 * 
	 * @return ModelAndView
	 */
	public function clear()
	{
		$this->view  = null;
		$this->model = null;
		
		return $this;
	}
	
	/**
	 * Determines whether this ModelAndView does not hold any view and does not
	 * contain a model.
	 * 
	 * @return bool
	 *         TRUE if this ModelAndView does not hold any view
	 *         and does not contain a model; otherwise, FALSE.
	 */
	public function isEmpty()
	{
		return ($this->view === null) && ($this->model === null);
	}
}